#include "ET++.ph"
#ifdef __GNUG__
#pragma implementation
#endif

#include "IAC.h"

#include "Class.h"
#include "Error.h"
#include "String.h"
#include "MemBuf.h"
#include "OrdColl.h"
#include "Env.h"
#include "System.h"
#include "WindowSystem.h"
#include "Timer.h"

bool gIACDebug;
IAC *gRpc;
SeqCollection *gServers;

//---- Request -----------------------------------------------------------------

static Request *pendingCommands;

Request::Request(int ser)
{
	serial= ser;
	buf= 0;
	len= 0;
	code= 0;
	next= 0;
}

Request::Request(const char *b, int l)
{
	if (l < 0)
		l= strlen(b);
	buf= strsave(b, l);
}

Request::~Request()
{
	SafeDelete(buf);
}

void Request::SetResult(int c, const char *msg, int l)
{
	code= c;
	if (l < 0)
		l= strlen(msg);
	len= l;
	buf= strsave(msg, l);

	if (gIACDebug)
		fprintf(stderr, "SetResult: %s\n", msg);
}

void Request::AddPending()
{
	next= pendingCommands;
	pendingCommands= this;
}

int Request::RemovePending(char **result, int *retlen)
{
	if (pendingCommands == this) {
		pendingCommands= next;
	} else {
		register Request *p;
		for (p= pendingCommands; p; p= p->next) {
			if (p->next == this) {
				p->next= next;
				break;
			}
		}
	}
	if (result)
		*result= buf;
	else {
		SafeDelete(buf);
	}
	if (retlen)
		*retlen= len;
	return code;
}

void SetCurrentCommand(int serial, int code, const char *msg, int len)
{
	register Request *p;
	if (gIACDebug)
		fprintf(stderr, "SetCurrentCommand: %d %d <%s>\n", serial, code, msg);
	for (p= pendingCommands; p; p= p->next) {
		if (serial == p->serial && p->buf == NULL) {
			p->SetResult(code, msg, len);
			break;
		}
	}
}

//---- IACImpl -----------------------------------------------------------------

IACImpl::IACImpl(IAC *s, const char *nm)
{
	sc= s;
	SetName(nm ? nm : BaseName(gProgname));
	errorcode= 0;
}

IACImpl::~IACImpl()
{
	SafeDelete(name);
}

void IACImpl::SetName(const char *nm)
{
	name= strsave(nm);
}

int IACImpl::Open(const char*)
{
	AbstractMethod("Open");
	return -1;
}

int IACImpl::Close(const char*)
{
	AbstractMethod("Close");
	return -1;
}

int IACImpl::LookupName(const char*, bool)
{
	AbstractMethod("LookupName");
	return -1;
}

int IACImpl::SendTo(int, int, const char*, int)
{
	AbstractMethod("SendTo");
	return 0;
}

int IACImpl::Broadcast(int, const char*, int)
{
	AbstractMethod("Broadcast");
	return 0;
}

int IACImpl::Poll(int, const char*, int)
{
	AbstractMethod("Poll");
	return 0;
}

int IACImpl::SendReply(int, int, int, const char*, int)
{
	AbstractMethod("SendReply");
	return 0;
}

void IACImpl::SendUp(bool isreply, int from, int serial, const char *buf, int len)
{
	if (len < 0)
		len= strlen(buf);
	if (isreply)
		SetCurrentCommand(serial, from, buf, len);
	else if (sc)
		sc->Dispatch2(from, serial, buf, len);
}

void IACImpl::SendUpError(int code)
{
	errorcode= code;
	fprintf(stderr, "IACImpl::SendUpError(%d)\n", errorcode);
	if (sc)
		sc->Error(errorcode);
}

//---- IAC ---------------------------------------------------------------------

NewMetaImpl(IAC,Object, (TP(name), TB(error), T(serial)));

IAC::IAC(const char *nm)
{
	gIACDebug= Env::GetValue("IAC.Debug", FALSE);
	name= strsave(nm ? nm : BaseName(gProgname));
	error= FALSE;
	serial= 100;
	rpc= gWindowSystem->MakeRpcImpl(this);
	sid= rpc->Open(name);
	if (gDebug)
		fprintf(stderr, "IAC::IAC: sid == %d\n", sid);
	sid= 2;
}

IAC::~IAC()
{
	rpc->Close(name);
	SafeDelete(name);
	SafeDelete(rpc);
}

void IAC::SetName(const char *n)
{
	strreplace(&name, n);
}

int IAC::LookupName(const char *nm, bool start)
{
	if (strcmp(name, nm) == 0)
		return sid;
	return rpc->LookupName(nm, start);
}

void IAC::Dispatch2(int from, int ser, const char *buf, int len)
{
	const char *args= strchr(buf, ' '); // find request/argument separator
	if (args) {
		args++;         // skip blank
		int l= args-buf;
		char *req= strsave(buf, l-1);
		Dispatch1(from, ser, req, args, len-l);
		delete req;
	} else
		Dispatch1(from, ser, buf, "", 0);
}

void IAC::Dispatch1(int from, int ser, const char *req, const char *buf, int len)
{
	if (gIACDebug)
		fprintf(stderr, "Dispatch1: from:%d ser:%d <%s> %d<%s>\n", from, ser,
																req, len, buf);
	if (gServers) {
		Iter next(gServers);
		Service *sv;
		char *servicename= strsave(req);
		char *cp= strchr(servicename, ':');
		if (cp)
			*cp++= 0;
		
		while (sv= (Service*) next()) {
			if (cp) {   // syntax:  service:request
				if (strcmp(servicename, sv->GetName()) == 0) { // found
					sv->ExtCommand(from, ser, this, cp, buf, len);
				}
			} else {    // syntax:  request
				sv->ExtCommand(from, ser, this, servicename, buf, len);
			}
		}
		delete servicename;
	}

	char *retbuf= 0;
	int retlen= -1;
	int rc= Dispatch(from, req, buf, len, retbuf, retlen);

	if (ser != 0 && retbuf)
		SendReply(from, ser, rc, retbuf, retlen);
}

int IAC::Broadcast(const char *req, const char *args, int l)
{
	return ISendTo(eBroadcast, 0, req, args, l);
}

int IAC::Broadcast(const char *name, const char *req, const char *args, int l)
{
	return ISendTo(eBroadcast, 0, form("%s:%s", name, req), args, l);
}

int IAC::SendTo(int to, const char *req, const char *args, int l)
{
	return ISendTo(eAsynchSend, to, req, args, l);
}

int IAC::SendTo(int to, const char *name, const char *req, const char *args, int l)
{
	return ISendTo(eAsynchSend, to, form("%s:%s", name, req), args, l);
}

int IAC::Talk(int to, const char *req, const char *args, int l,
													char **retbuf, int *retlen)
{
	return ISendTo(eSynchSend, to, req, args, l, retbuf, retlen);
}

int IAC::Talk(int to, const char *name, const char *req,
						const char *args, int l, char **retbuf, int *retlen)
{
	return ISendTo(eSynchSend, to, form("%s:%s", name, req), args, l, retbuf, retlen);
}

int IAC::Talk(const char *prog, const char *va_(fmt), ...)
{
	int rc;
	va_list ap;
	va_start(ap, va_(fmt));

	int to= LookupName(prog, TRUE);

	if (to != -1) {
		char *bp, buf[2000];
		sprintf(buf, "%s:", prog);
		vsprintf(&buf[strlen(buf)], va_(fmt), ap);
		if (bp= strchr(buf, ' '))
			*bp++= 0;
		rc= SendTo(to, buf, bp);
	} else
		rc= -1;

	va_end(ap);
	return rc;
}

int IAC::ISendTo(IACSendType st, int to, const char *req,
						const char *buf, int l, char **retbuf, int *retlen)
{
	Request pending(++serial);
	pending.AddPending();

	if (st != eBroadcast && to == sid) {
		Dispatch1(to, serial, req, buf, l);
	} else if (to != -1) {
		int rc, rl= 0, tl;
		char *b;
		bool dodelete= FALSE;

		if (req)
			rl= strlen(req);

		if (buf) {
			if (l < 0)
				l= strlen(buf);
		} else
			l= 0;

		if (rl > 0 && l > 0) {
			tl= l + rl + 2;
			b= new char[tl];
			dodelete= TRUE;
		
			if (rl > 0)
				strcpy(b, req);
			if (l > 0 && rl > 0)
				strcat(b, " ");
			if (l > 0)
				strcat(b, buf);
		} else if (rl > 0) {
			tl= rl + 1;
			b= (char*)req;
		} else if (l > 0) {
			tl= l + 1;
			b= (char*)buf;
		}

		if (gIACDebug)
			fprintf(stderr, "ISendTo: to:%d ser:%d %d<%s>\n", to, serial, tl, b);

		switch (st) {
		case eBroadcast:
			rc= rpc->Broadcast(serial, b, tl);
			break;
		case ePoll:
			rc= rpc->Poll(serial, b, tl);
			break;
		default:
			rc= rpc->SendTo(to, serial, b, tl);
			break;
		}

		if (dodelete && b)
			delete b;

		if (rc >= 0 && st == eSynchSend && to > 0) {
			Timer rt(5000);
			while (pending.buf == NULL) {
				gSystem->InnerLoop();
				if (rt.WasTimeout())
					pending.SetResult(-2, "timeout");
			}
		}
	}
	return pending.RemovePending(retbuf, retlen);
}

int IAC::SendReply(int to, int ser, int code, const char *b, int l)
{
	if (gIACDebug)
		fprintf(stderr, "SendReply: to:%d ser:%d %d<%s>\n", to, ser, l, b);
	return rpc->SendReply(to, ser, code, b, l);
}

int IAC::Dispatch(int, const char*, const char*, int, char *&, int&)
{
	AbstractMethod("Dispatch");
	return -1;
}

void IAC::Error(int code)
{
	fprintf(stderr, "IAC::Error(%d)\n", code);
}

//---- Service -----------------------------------------------------------------

NewMetaImpl(Service,SysEvtHandler, (TP(name)));

Service::Service(const char *nm) : SysEvtHandler(0)
{
	name= strsave(nm);
	omb= new MemBuf;
	os= new OStream(omb);
	if (gServers == 0)
		gServers= new OrdCollection;
	gServers->Add(this);
}

Service::~Service()
{
	SafeDelete(name);
	SafeDelete(is);
	SafeDelete(os);
	if (gServers) {
		gServers->RemovePtr(this);
		if (gServers->Size() <= 0) {
			delete gServers;
			gServers= 0;
		}
	}
}

int Service::Talk(int to, const char *request)
{
	char *buf= 0;
	int len= 0;

	os->flush();
	omb->SwitchToRead();

	gRpc->Talk(to, name, request, omb->Base(), (int)omb->ContentsSize(), &buf, &len);

	SafeDelete(os);
	omb= new MemBuf;
	os= new OStream(omb);
		
	SafeDelete(is);
	imb= new MemBuf;
	is= new IStream(imb);
		
	if (buf) {
		imb->sputn(buf, len);
		imb->SwitchToRead();
		//SafeDelete(buf);
	}

	return 0;
}

int Service::SendTo(int to, const char *request)
{
	os->flush();
	omb->SwitchToRead();

	gRpc->SendTo(to, name, request, omb->Base(), (int)omb->ContentsSize());

	SafeDelete(os);
	omb= new MemBuf;
	os= new OStream(omb);
		
	return 0;
}

int Service::Broadcast(const char *request)
{
	os->flush();
	omb->SwitchToRead();

	gRpc->Broadcast(name, request, omb->Base(), (int)omb->ContentsSize());

	SafeDelete(os);
	omb= new MemBuf;
	os= new OStream(omb);
		
	return 0;
}

void Service::ExtCommand(int from, int serial, IAC *sc,
				const char *req, const char *args, int arglen)
{
	MemBuf *mb= new MemBuf;
	OStream os(mb);

	MemBuf *mb1= new MemBuf;
	mb1->sputn(args, arglen);
	mb1->SwitchToRead();
	IStream is(mb1);

	int rc= Dispatch(from, req, is, os);

	os.flush();
	mb->SwitchToRead();
	if (mb->ContentsSize() > 0)
		sc->SendReply(from, serial, rc, mb->Base(), (int) mb->ContentsSize());
}

int Service::Dispatch(int, const char *req, IStream &is, OStream &os)
{
	if (strcmp(req, "test") == 0) {
		int code;
		is >> code;
		os << code;
		return 0;
	}
	fprintf(stderr, "Service: %s: unknown request: %s\n", name, req);
	return -1;
}

void Service::InspectorId(char *buf, int bufSize)
{
	strn0cpy(buf, GetName(), bufSize);
}

